Glide源码解析 您所在的位置:网站首页 glide 面试 Glide源码解析

Glide源码解析

2024-01-03 14:21| 来源: 网络整理| 查看: 265

本次源码解析基于4.12.0,如有描述错误,请大佬们评论指出。

一、Glide的用法 // RecyclerView中加载图片 @Override public void onBindViewHolder(PhotoViewHolder holder, int position) { GlideApp.with(holder.itemView).load(list.get(position)) .transform(new RoundedCorners(40)) .diskCacheStrategy(DiskCacheStrategy.RESOURCE) .placeholder(R.drawable.ic_launcher) .error(R.drawable.ic_launcher) .into(holder.imageView); } 二、Glide一些面试常考点 2.1、 Glide如何感知Application、Activity、Fragment的生命周期?

Q:先问下如果你想感知application的那几个内存不足的方法,你会怎么做。 ComponentCallbacks2是系统提供的类。 image.png image.png

Application类管理这些订阅者,方法回调时,遍历通知。 image.png

Glide中的trimMemory收到事件通知后的已做处理,不需要我们自己再去清理Glide的资源占用。 image.png

Q:页面如果有ImageView,加载图片时立马页面关闭/返回,此时应该停止加载,Glide如何感知Activity/Fragment的onDestroy呢?

当然是在对应的Act或者Fragment中插入空白SupportRequestManagerFragment实现。

GlideApp.with(activity).load("https://t7.baidu.com/it/u=3652245443,3894439772&fm=193&f=GIF").into(view); GlideApp.with(fragment).load("https://t7.baidu.com/it/u=3652245443,3894439772&fm=193&f=GIF").into(view);

image.png

image.png 如果说我们的Activity有个ImageView,它里面有两个Fragment也加载ImageView,按照规范,with方法应该是基于ImageView所处的context来决定,该传Act就传Act,该传Fragment就传Framgent没错,这样一来,Glide就嵌入3个SupportRequestManagerFragment进入了我们的页面。有点厉害哦。

但是如果Fragment里面不小心写成了下面这样

//fragment中的ImageView GlideApp.with(view).load("https://t7.baidu.com/it/u=3652245443,3894439772&fm=193&f=GIF").into(view);

用一个简单的demo模拟这种场景,Glide从View去找Fragment竟然找不到, 既然找不到View所在的Fragment容器,那就只能用跟Activity保持一致了 所以小伙子们写with方法的时候要注意啊。(记得之前我用过kotlin中Fragment拓展方法,可以通过View找到其所在的Fragment)

image.png image.png

2.2、 Glide的MemoryCache(LruResourceCache)和LruBitmapPool以及DiskLruCache默认size多大呢? final int MEMORY_CACHE_TARGET_SCREENS = 2; final int BITMAP_POOL_TARGET_SCREENS =Build.VERSION.SDK_INT < Build.VERSION_CODES.O ? 4 : 1; int widthPixels =context.getResources().getDisplayMetrics().widthPixels; int heightPixels =context.getResources().getDisplayMetrics().heightPixels; int screenSize = widthPixels * heightPixels * BYTES_PER_ARGB_8888_PIXEL; //8及其8以上4张图图片的size int targetBitmapPoolSize = Math.round(screenSize * BITMAP_POOL_TARGET_SCREENS); //2张屏幕大小的size int targetMemoryCacheSize = Math.round(screenSize * MEMORY_CACHE_TARGET_SCREENS); int DEFAULT_DISK_CACHE_SIZE = 250 * 1024 * 1024; String DEFAULT_DISK_CACHE_DIR = "image_manager_disk_cache"; File cacheDirectory = context.getCacheDir();

LruResourceCache默认: 只有2张屏幕大小的图片size; LruBitmapPool默认: 只有1 or 4张的屏幕大小的Bitmap可以复用; DiskLruCache默认: 在内置SD卡且占用空间250M。

image.png

2.3、 三级缓存的添加和移除发生在什么时机?

弱引用缓存(ResourceWeakReference)   内存缓存(LruResourceCache)   磁盘缓存(DiskLruCache)

同一个Bitmap一旦从LruResourceCache取出(remove)了,那它就会进行弱引用缓存了,如果弱引用清除时,就是LruCache加入缓存时,看样子二者不能共存?

目前测试的结果:一旦图片加载ok了,先加入弱引用缓存,如果是recyclerView列表,item复用,ImageView会被into很多次,该Bitmap对应的弱引用早就没了,此时Bitmap会加入LruResourceCache。如果不是列表是页面加载的ImageView,当页面关闭时,Bitmap的弱引用清除,此时会加入LruResourceCache。如果LruResourceCache内存不够,那就进行trim。

DiskLruCache下文讲。

2.4、 Glide如何区分一个Url的内容是png,还是jpg,还是gif的呢?

先让大家看一下内容,后面会贯通讲下。

image.png

2.5、  设置BitmapFactory.Options.inBitmap作用

BitmapFactory.Options.inBitmap = lruBitmapPool.getDirty(width, height, expectedConfig) inBitmap表示要复用Bitmap,该Bitmap就来自LruBitmapPool,由于这个池本身容纳的bitmap数量有限,能提供还好,不能提供它还是直接createBitmap(新创建)返回Bitmap。

Q:如果BitmapPool中的有Bitmap存在,是不是一定可以复用?有没有啥限制?

//缓冲池策略 private static LruPoolStrategy getDefaultStrategy() { final LruPoolStrategy strategy; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { strategy = new SizeConfigStrategy(); } else { strategy = new AttributeStrategy(); } return strategy; } //SizeConfigStrategy 高版本就是size config做key public boolean equals(Object o) { if (o instanceof Key) { Key other = (Key) o; return size == other.size && Util.bothNullOrEqual(config, other.config); } return false; } //AttributeStrategy 低版本严格按照宽高config做key public boolean equals(Object o) { if (o instanceof Key) { Key other = (Key) o; return width == other.width && height == other.height && config == other.config; } return false; } public Bitmap getDirty(int width, int height, Bitmap.Config config) { final Bitmap result = strategy.get(width, height, config != null ? config : Bitmap.Config.ARGB_8888); if (result == null) { ...... } else { ..... currentSize -= strategy.getSize(result); tracker.remove(result); //如果bitmap被选中,那么一定会从池中remove出来 bitmap.setHasAlpha(true); //选择的bitmap要做点处理 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { bitmap.setPremultiplied(true); //虽不知道有啥用,以后如果有复用bitmap场景,也这么干 } } //找不到合适的bitmap,那就创建一个新的 if (result == null) { result = Bitmap.createBitmap(width, height, config != null ? config : Bitmap.Config.ARGB_8888); } return result; }

LruBitmapPool根据系统版本,来挑选合适的bitmap,低于5.0版本有着严格的限制(宽、高、config要完全匹配才行),5.0及其以上,保证size、config 一致就行。config就是我们说的Bitmap.Config.ARGB_8888这种。

// 获取bitmap的size bitmap.getAllocationByteCount() //优先选用这个 bitmap.getHeight() * bitmap.getRowBytes()

如果LruBitmapPool没有找到合适,那就create新Bitmap,往往就是这里创建一个新Bitmap分配内存时导致OOM。有没得可以避免的方法呢???奔溃来自6.0机子

image.png

三、从首次加载网络图片来看Glide的处理流程 GlideApp.with(view).load("https://t7.baidu.com/it/u=3652245443,3894439772&fm=193&f=GIF").into(view);

GlideApp是通过Glide的注解生成的,可以通过注解配置使用okhttp去下载图片,也可以配置一些全局设置,懒得设置就用默认的也行,具体可以参看Glide官方demo,很全,(不要说不会,不会,就是你没下载官方demo)

GlideApp.with(view) 就是基于view去获取对应的RequestManager,如果在子线程调用这个,那就是单例的RequestManager,如果是其他比如Act或者Fragment的话,就是RequestManager的一个实例。Application对应的RequestManager关注的是Application的生命周期那几个内存不足的方法。

.load(list.get(position)) .transform(new RoundedCorners(40)) .diskCacheStrategy(DiskCacheStrategy.RESOURCE) .placeholder(R.drawable.ic_launcher) .error(R.drawable.ic_launcher)

load的话,不写asBitmap或者asGif那默认就是asDrawable

public RequestBuilder load(@Nullable String string) { return asDrawable().load(string); }

能够这么...链式配置参数,肯定是Builder设计模式了(也不一定,rxjava就是套娃实现的) image.png image.png

RequestBuilder上的泛型TranscodeType就是Drawable、Bitmap、File、GifDrawable image.png

这里说下缩略图,缩略图有两种,一种是直接用float参数的,第二种是传入一个新的ReqeustBuilder。错误error也可以传入RequestBuilder,一旦传入新的RequestBuilder,那构建请求时,会创建多个request。 image.png

.into(holder.imageView);

into后,就真正开始buildRequest了,距离真正发请求获取图片还有很多准备事要干。

3.1、请求前的准备工作 3.1.1、获取ImageView的scaleType类型

可用于后面对Bitmap的转换。

public ViewTarget into(@NonNull ImageView view) { ...... switch (view.getScaleType()) { case CENTER_CROP: requestOptions = requestOptions.clone().optionalCenterCrop(); break; case CENTER_INSIDE: requestOptions = requestOptions.clone().optionalCenterInside(); break; case FIT_CENTER: case FIT_START: case FIT_END: requestOptions = requestOptions.clone().optionalFitCenter(); break; case FIT_XY: requestOptions = requestOptions.clone().optionalCenterInside(); break; case CENTER: case MATRIX: ...... //transcodeClass就是上面提到TranscodeType的Class return into(glideContext.buildImageViewTarget(view, transcodeClass), null,requestOptions, Executors.mainThreadExecutor()); } } 3.1.2、构建Request: 包含主Reqeust、缩略图、错误时要显示的图片。

主Reqeust跟缩略图组合,那他们一起开始请求即可。 错误的图片要等主Reqeust失败后才开始。 很巧妙(鸡贼)的是,主请求跟缩略图的组合是ThumbnailRequestCoordinator,其继承Request。同样ErrorRequestCoordinator也是继承Reqeust。

//主Reqeust跟缩略图组合 public void begin() { synchronized (requestLock) { isRunningDuringBegin = true; try { if (fullState != RequestState.SUCCESS && thumbState != RequestState.RUNNING) { thumbState = RequestState.RUNNING; thumb.begin(); } if (isRunningDuringBegin && fullState != RequestState.RUNNING) { fullState = RequestState.RUNNING; full.begin(); } } finally { isRunningDuringBegin = false; } } } //错误的图片 @Override public void onRequestFailed(Request request) { synchronized (requestLock) { if (!request.equals(error)) { primaryState = RequestState.FAILED; if (errorState != RequestState.RUNNING) { errorState = RequestState.RUNNING; error.begin(); } return; } ...... } 3.1.3、判断构建的请求和ImageView之前的请求是否一致且是否用内存缓存,是的话就用之前的 private Y into(...) { Preconditions.checkNotNull(target); Request request = buildRequest(target, targetListener, options, callbackExecutor); Request previous = target.getRequest(); if (request.isEquivalentTo(previous) && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) { if (!Preconditions.checkNotNull(previous).isRunning()) { previous.begin(); } return target; } requestManager.clear(target); target.setRequest(request); requestManager.track(target, request); return target; }

requestManager.clear(target) 这里是清除掉target(就是ImageView)之前的请求,比如我们的ImageView滑出了屏幕,此时Item复用,再次into时,先clear之前的Request,所以无需我们手动去cancel请求。

target.setRequest(request) 就是给View设置Tag,早些年,给列表中的ImageView直接设置Tag显示被占用,就是这里被占用了,不过现在它设置是指定了tagId(R.id.glide_custom_view_target_tag)。

requestManager.track(target, request) 请求开始---> request.begin()。

3.1.4、获取View宽高(目标宽高,如果下载的图片大小跟目标宽高不一致,就要做矩阵缩放)且加载占位图 @Override public void begin() { synchronized (requestLock) { ...... //overrideWidth, overrideHeight是合法的宽高 if (Util.isValidDimensions(overrideWidth, overrideHeight)) { onSizeReady(overrideWidth, overrideHeight); } else { //走回调获取ImageVide的宽高 target.getSize(this); } if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)&& canNotifyStatusChanged()) { // 开始加载占位图 target.onLoadStarted(getPlaceholderDrawable()); } } }

获取宽高有两种方式:一种是你在设置参数时,通过override(int width, int height)配置的宽高,另外一种是它从ImageView上测量获得宽高。(获取宽高的时机以及对ImageView的padding的处理)

void getSize(@NonNull final SizeReadyCallback cb) { .....//代码做过精简处理 view.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() { @Override public boolean onPreDraw() { int horizontalPadding = view.getPaddingLeft() + view.getPaddingRight(); int verticalPadding = view.getPaddingTop() + view.getPaddingBottom(); int currentWidth = view.getWidth() - horizontalPadding; int currentHeight = view.getHeight() - verticalPadding; cb.onSizeReady(currentWidth, currentHeight); return true; } }); } 3.2、Engine开始load,处理请求 3.2.1、先尝试从内存中找EngineResource public LoadStatus load(...) { //构建key,看好了,与width height有关哈。 EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations, resourceClass, transcodeClass, options); EngineResource memoryResource; synchronized (this) { //先从内存缓寸中加载 memoryResource = loadFromMemory(key, isMemoryCacheable, startTime); if (memoryResource == null) { //可能从磁盘 可能从网络中找 后面详细说 ...... } //有则直接回调给用户,结束 cb.onResourceReady( memoryResource, DataSource.MEMORY_CACHE, false); return null; }

这里细节很多,大家注意。

从内存缓存开始,先从弱引用开始查找EngineResource,没有再从ResourceLruCache查找。弱引用和ResourceLruCache二者查找时用的key是一样的,看样子EngineResource不可能同时存在于在弱引用和ResourceLruCache中了。\color{#ff0000}{看样子EngineResource不可能同时存在于在弱引用和ResourceLruCache中了。}看样子EngineResource不可能同时存在于在弱引用和ResourceLruCache中了。

private EngineResource loadFromMemory(EngineKey key...) { if (!isMemoryCacheable) { //跳过缓存 return null; } //从弱引用ResourceWeakReference中查找 EngineResource active = = activeResources.get(key); if (active != null) { active.acquire(); //资源被使用,引用++ return active; } //从MemoryCache中找,找出来就是从LruCache中移除,remove的返回值就是啊 EngineResource cached = cache.remove(key); final EngineResource result; if (cached == null) { result = null; } else if (cached instanceof EngineResource) { result = (EngineResource) cached; } else { result = new EngineResource( cached, true,true, key,this); } if (result != null) { //资源被使用,引用++ 且 添加到弱引用中 result.acquire(); activeResources.activate(key, result); return result; } return null; } 3.2.2、从内存中找不到,再看要不要从磁盘或者网络加载 //有同样的request在处理,直接复用之前的 EngineJob current = jobs.get(key, onlyRetrieveFromCache); if (current != null) { current.addCallback(cb, callbackExecutor); return new LoadStatus(cb, current); } EngineJob engineJob = engineJobFactory.build(key,isMemoryCacheable, useUnlimitedSourceExecutorPool,useAnimationPool,onlyRetrieveFromCache); DecodeJob decodeJob = decodeJobFactory.build(glideContext,model,key,signature,width, height,resourceClass,transcodeClass,priority,diskCacheStrategy,transformations, isTransformationRequired,isScaleOnlyOrNoTransform,onlyRetrieveFromCache,options, engineJob); jobs.put(key, engineJob); engineJob.addCallback(cb, callbackExecutor); engineJob.start(decodeJob); public synchronized void start(DecodeJob decodeJob) { ...... //感觉这里总是true,如果状态为Stage.INITIALIZE,下一个肯定是磁盘缓存中的一个 Stage firstStage = getNextStage(Stage.INITIALIZE); boolean res= firstStage == Stage.RESOURCE_CACHE || firstStage == Stage.DATA_CACHE GlideExecutor executor = res ? diskCacheExecutor : getActiveSourceExecutor(); executor.execute(decodeJob); }

来看看他们的状态流转,递归方法,本地可能会存在两种类型的DiskLruCache,一种是源数据(他是DATA_CACHE),一种是基于目标宽高的将原图做一次转换后保存的数据(RESOURCE_CACHE)。是的,你没看错,源数据是Data,转换后的数据是Resource。那么在加载磁盘缓存时,优先尝试RESOURCE_CACHE,没得,就尝试DATA_CACHE,都没得再开始老老实实走网络。我们配置DiskCacheStrategy默认是AUTOMATIC,这里diskCacheStrategy. decodeCachedResource()就是true。

优先尝试RESOURCE_CACHE就是走ResourceCacheGenerator的startNext方法。 再次尝试DATA_CACHE就是走DataCacheGenerator的startNext方法。 想从网络加载就走SourceGenerator的startNext方法。

如果用户设置的是DiskCacheStrategy.AUTOMATIC或者DiskCacheStrategy.DATA,那么SourceGenerator的startNext会走两次,第一次是从网络上下载资源返回流数据,第二次再次走startNext方法,是先将流存到磁盘缓存里面,再转入到DataCacheGenerator的startNext方法,从本地缓存File获取ByteBuffer开启后面解析流程。

如果用户设置的是DiskCacheStrategy.RESOURCE,那么SourceGenerator的startNext会只会走一次,从网络中下载资源获取流数据后,就开始后面的解析流程。

//线程池执行decodeJob,那decodeJob就是个是Runnable,优先看其run方法。 public void run() { switch (runReason) { //runReason默认就是 INITIALIZE case INITIALIZE: stage = getNextStage(Stage.INITIALIZE); currentGenerator = getNextGenerator(); runGenerators(); break; case SWITCH_TO_SOURCE_SERVICE: runGenerators(); break; case DECODE_DATA: Log.e("test","下载后开始解码数据"); decodeFromRetrievedData(); break; ..... } } private Stage getNextStage(Stage current) { switch (current) { case INITIALIZE: return diskCacheStrategy.decodeCachedResource() ? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE); case RESOURCE_CACHE: return diskCacheStrategy.decodeCachedData() ? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE); ...... } } private DataFetcherGenerator getNextGenerator() { switch (stage) { case RESOURCE_CACHE: return new ResourceCacheGenerator(decodeHelper, this); case DATA_CACHE: return new DataCacheGenerator(decodeHelper, this); case SOURCE: return new SourceGenerator(decodeHelper, this); case FINISHED: ..... } }

最关键的runGenerators方法中的currentGenerator.startNext(),就是上面的那几个startNext。

private void runGenerators() { boolean isStarted = false; while (!isCancelled && currentGenerator != null && !(isStarted = currentGenerator.startNext())) { stage = getNextStage(stage); currentGenerator = getNextGenerator(); if (stage == Stage.SOURCE) { reschedule(); return; } } ...... }

首先肯定是尝试ResourceCacheGenerator的startNext方法,特别关键,这里涉及到Model,没读太仔细,没法讲,只能提个大概,他里面的modelLoader在初始化Glide时在Registry中加了很多类,append了很多很多。

public boolean startNext() { .... while (modelLoaders == null || !hasNextModelLoader()) { ...... //拼装出来的key currentKey = new ResourceCacheKey( helper.getArrayPool(), sourceId, helper.getSignature(), helper.getWidth(), helper.getHeight(), transformation, resourceClass,helper.getOptions()); //首次的话,从磁盘缓存中取肯定找不到这个key对应的文件 cacheFile = helper.getDiskCache().get(currentKey); if (cacheFile != null) { sourceKey = sourceId; modelLoaders = helper.getModelLoaders(cacheFile); modelLoaderIndex = 0; } } ...... //如果存在缓存文件就去加载缓存文件 }

再次尝试DataCacheGenerator的startNext方法,它的key很简单,就是url,这里的sourceId就是url。 首次肯定没有缓存,等SourceGenerator下载好后,再流转到这里来处理。

@Override public boolean startNext() { while (modelLoaders == null || !hasNextModelLoader()) { Key sourceId = cacheKeys.get(sourceIdIndex); //这个key跟文件下载后缓存到磁盘是一样的key Key originalKey = new DataCacheKey(sourceId, helper.getSignature()); cacheFile = helper.getDiskCache().get(originalKey); if (cacheFile != null) { this.sourceKey = sourceId; //url就是key modelLoaders = helper.getModelLoaders(cacheFile); modelLoaderIndex = 0; } } loadData = null; boolean started = false; while (!started && hasNextModelLoader()) { ModelLoader modelLoader = modelLoaders.get(modelLoaderIndex++); loadData = modelLoader.buildLoadData(cacheFile, helper.getWidth(), helper.getHeight(), helper.getOptions()); if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) { started = true; //真正触发流解析的位置是这里哈。 loadData.fetcher.loadData(helper.getPriority(), this); } } return started; }

最后看看SourceGenerator的startNext,它的startNext会走两次,第一次获取数据,第二次是为了缓存源数据,很明显它修改了runReason,然后在新的线程池去跑DecodeJob,再次看decodejob的run方法。

public boolean startNext() { //dataToCache不为空就可以缓存文件文件到本地 if (dataToCache != null) { Object data = dataToCache; dataToCache = null; cacheData(data); }//缓存完数据后他就不为空了 其实sourceCacheGenerator=new DataCacheGenerator(xxx) //存了之后再次会走DataCacheGenerator的startNext方法,缓存不为空了,就开始加载本地文件 if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) { return true; } sourceCacheGenerator = null; loadData = null; boolean started = false; while (!started && hasNextModelLoader()) { loadData = helper.getLoadData().get(loadDataListIndex++); if (loadData != null && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource()) || helper.hasLoadPath(loadData.fetcher.getDataClass()))) { started = true; loadData.fetcher.loadData(xxxx) } } return started; }

MultiModelLoader-->HttpUrlFetcher.loadData从网络上加载数据,加载好后,就回调准备再次调用startNext

loadData.fetcher.loadData( helper.getPriority(), new DataCallback() { @Override public void onDataReady(@Nullable Object data) { if (isCurrentRequest(toStart)) { onDataReadyInternal(toStart, data); } } @Override public void onLoadFailed(@NonNull Exception e) { if (isCurrentRequest(toStart)) { onLoadFailedInternal(toStart, e); } } }); void onDataReadyInternal(LoadData loadData, Object data) { DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy(); if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) { dataToCache = data; //此时赋值,SourceGenerator的startNext再次调用就不为空了。 //再次reschedule的原因就是切线程到glide的线程池中线程,因为之前加载网络数据可能是用户自己开的线程 //就是这里会触发SourceGenerator的startNext再次调用 cb.reschedule(); } else { //如果是非DATA和AUTOMATIC类型,比如Resource类型,那么不用回调,直接 //拿着这个data(ContentLengthInputStream)去操作 //看起来比DATA和AUTOMATIC轻便不少啊。 cb.onDataFetcherReady(loadData.sourceKey,data,loadData.fetcher, loadData.fetcher.getDataSource(),originalKey); } } @Override public void reschedule() { runReason = RunReason.SWITCH_TO_SOURCE_SERVICE; getActiveSourceExecutor().execute(job); }

image.png

第二次调用SourceGenerator的startNext就准备缓存到磁盘,这个缓存的就是源数据。

private void cacheData(Object dataToCache) { try { Encoder encoder = helper.getSourceEncoder(dataToCache); DataCacheWriter writer = new DataCacheWriter(encoder, dataToCache, helper.getOptions()); originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature()); helper.getDiskCache().put(originalKey, writer); } finally { loadData.fetcher.cleanup(); } sourceCacheGenerator = new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this); }

存了之后,就用NIO的方式读取刚刚缓存在磁盘里面的文件,这一套操作,是不是有点慢了,先存到本地再读取file获取ByteBuffer。

image.png

3.2.3、根据DirectByteBuffer解码出Resource(Bitmap)

我们这里load进去的url就是一张图片,对应三条解码路径:

DirectByteBuffer->GifDrawable->Drawable DirectByteBuffer->Bitmap->Drawable DirectByteBuffer->BitmapDrawable->Drawable 但是不确定是哪一条,那就都试试,发现每次都从gif类型(ByteBufferGifDecoder)开始,不知是不是特意为之,如果类型不匹配就换下一个。 private Resource decodeResourceWithList(DataRewinder rewinder....) throws GlideException { Resource result = null; for (int i = 0, size = decoders.size(); i < size; i++) { ResourceDecoder decoder = decoders.get(i); try { DataType data = rewinder.rewindAndGet(); if (decoder.handles(data, options)) { //gif类型需要通过获取文件类型判断,bitmap则直接true data = rewinder.rewindAndGet(); //重置buffer读取的位置到起始位置 result = decoder.decode(data, width, height, options); } } catch (IOException | RuntimeException | OutOfMemoryError e) { exceptions.add(e); } if (result != null) { break; } } if (result == null) { throw new GlideException(failureMessage, new ArrayList(exceptions)); } return result; }

image.png

解析ByteBuffer的文件类型关键代码来了:

// DefaultImageHeaderParser @NonNull private ImageType getType(Reader reader) throws IOException { try { final int firstTwoBytes = reader.getUInt16(); // JPEG.类型读取两个字节就可以判断了 if (firstTwoBytes == EXIF_MAGIC_NUMBER) { return JPEG; } //gif要读3字节 final int firstThreeBytes = (firstTwoBytes 相当于拿到bitmap Resource decoded = decodeResource(rewinder, width, height, options); //对bitmap做转换 Resource transformed = callback.onResourceDecoded(decoded); return transcoder.transcode(transformed, options); }

callback.onResourceDecoded(decoded)很关键

Resource onResourceDecoded(DataSource dataSource, @NonNull Resource decoded) { Class resourceSubClass = (Class) decoded.get().getClass(); Transformation appliedTransformation = null; Resource transformed = decoded; //磁盘缓存策略在这里发挥作用 if (dataSource != DataSource.RESOURCE_DISK_CACHE) { //选取其中一个跟Bitmap匹配的Transformation操作 appliedTransformation = decodeHelper.getTransformation(resourceSubClass); //应用操作 transformed = appliedTransformation.transform(glideContext, decoded, width, height); } //应用完之后,旧的bitmap直接让其回收 if (!decoded.equals(transformed)) { decoded.recycle(); } ...... //DiskCacheStrategy.DATA的isResourceCacheable默认就是false了 //DiskCacheStrategy.AUTOMATIC经过了几重不明所以的判断,isFromAlternateCacheKey=false,导致也是false //但是不影响,因为之前已经在本地缓存过一次源数据了 //所以这里专门为DiskCacheStrategy.RESOURCE和DiskCacheStrategy.ALL使用 if (diskCacheStrategy.isResourceCacheable(isFromAlternateCacheKey, dataSource, encodeStrategy)) { ..... final Key key; switch (encodeStrategy) { case SOURCE: //源数据,,不太可能会走这个逻辑 key = new DataCacheKey(currentSourceKey, signature); break; case TRANSFORMED: //转换后的bitmap对应的key key = new ResourceCacheKey(decodeHelper.getArrayPool(), currentSourceKey, signature, width, height,appliedTransformation, resourceSubClass, options); break; ..... } LockedResource lockedResult = LockedResource.obtain(transformed); //拿到key,但是没有做缓存操作,因为defer是延迟处理的,后面会很快存转换后的数据到磁盘 deferredEncodeManager.init(key, encoder, lockedResult); result = lockedResult; } return result; }

image.png image.png 圆角的处理,以后有这种需求,也这么干。

3.2.5、通知bitmap就绪了且按需保存转换的数据到磁盘。 private void decodeFromRetrievedData() { Resource nresource = decodeFromData(currentFetcher, currentData, currentDataSource); notifyEncodeAndRelease(resource, currentDataSource, isLoadingFromAlternateCacheKey); } //resource就是bitmap private void notifyEncodeAndRelease(Resource resource, DataSource dataSource, boolean isLoadedFromAlternateCacheKey) { if (resource instanceof Initializable) { // bitmap.prepareToDraw(); 预先将bitmap加载到gpu上 ((Initializable) resource).get().prepareToDraw(); } .... //通知engine以及回调给用户onResourceReady .... //这里真正开始写入转换的后的数据 if (deferredEncodeManager.hasResourceToEncode()) { deferredEncodeManager.encode(diskCacheProvider, options); } ..... } //deferredEncodeManager //这里真正开始写入转换的后的数据 void encode(DiskCacheProvider diskCacheProvider, Options options) { GlideTrace.beginSection("DecodeJob.encode"); try { //bitmap缓存为file diskCacheProvider.getDiskCache().put(key, new DataCacheWriter(encoder, toEncode, options)); } finally { toEncode.unlock(); GlideTrace.endSection(); } } @Override public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition resource) { //资源释放的时候,就清空弱引用先将它放入队列里面的 activeResources.deactivate(cacheKey); if (resource.isMemoryCacheable()) { //资源释放的时候,弱引用清楚,此时Lru缓存加入进去 cache.put(cacheKey, resource); } ..... } synchronized void deactivate(Key key) { ResourceWeakReference removed = activeEngineResources.remove(key); if (removed != null) { removed.reset(); } }

页面关闭/返回 image.png

recyclerView列表滑动 image.png

那MemoryCache缓存中何时取出,又是何时添加的 其实就是在发起请求前,Engine先从内存缓存中取,有就直接通知回调,没有就走后面一系列流程。

private EngineResource loadFromMemory(EngineKey key...) { if (!isMemoryCacheable) { //跳过缓存 return null; } //从弱引用ResourceWeakReference中查找 EngineResource active = = activeResources.get(key); if (active != null) { active.acquire(); //资源被使用,引用++ return active; } //从MemoryCache中找,找出来就是从LruCache中移除,remove的返回值就是啊 EngineResource cached = cache.remove(key); final EngineResource result; if (cached == null) { result = null; } else if (cached instanceof EngineResource) { result = (EngineResource) cached; } else { result = new EngineResource( cached, true,true, key,this); } if (result != null) { //资源被使用,引用++ 且 添加到弱引用中 result.acquire(); activeResources.activate(key, result); return result; } return null; }

看样子,资源弱引用存在,那LruResourceCache就不可能存在这个资源,二者属于不同阶段的一个相互补充,没得交集。

五、后续

本期只是针对load(url)做了一个简单的操作流转的记录,这个记录贯穿了一系列的知识点,对Glide的了解还是比较浅,后续对其ModelLoader、Gif、video加载这块,也做个补充吧。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有